class Observer {
  constructor(data, callback) {
    this.originalData = data;
    this.callback = callback;
    this.walk(data);
    this.proxy = new Proxy(this.data, {
      get: (target, key, receiver) => {
        return Reflect.get(target, key, receiver);
      },
      set: (target, key, value, receiver) => {
        const oldVal = target[key];
        if (oldVal === value) {
          return true;
        }
        const result = Reflect.set(target, key, value, receiver);
        if (!Object.prototype.hasOwnProperty.call(target, key)) {
          this.walk(value);
        }
        this.callback && this.callback(key, value, oldVal);
        return result;
      },
      deleteProperty: (target, key) => {
        const result = Reflect.deleteProperty(target, key);
        this.callback && this.callback(key, undefined, target[key]);
        return result;
      },
    });
  }

  walk(data) {
    if (!data || typeof data !== "object") {
      return;
    }
    this.data = {};
    for (const key in data) {
      if (Object.prototype.hasOwnProperty.call(data, key)) {
        let val = data[key];
        if (typeof val === "object" && val !== null) {
          val = new Observer(val, this.callback).proxy;
        }
        Object.defineProperty(this.data, key, {
          enumerable: true,
          configurable: true,
          get: () => val,
          set: (newVal) => {
            if (val === newVal) {
              return;
            }
            val = newVal;
            if (typeof val === "object" && val !== null) {
              val = new Observer(val, this.callback).proxy;
            }
            this.callback && this.callback(key, newVal, val);
          },
        });
      }
    }
  }
}
module.exports = Observer;
